#!/usr/bin/env bash
#
# Format C/C++ code using clang-format.
#
# To ensure reproducible formatting this script checks that clang-format
# matches the lowest version number of LLVM supported by Scala Native.
#
# Usage: $0 [--test]
#
# Set CLANG_FORMAT_PATH to configure path to clang-format.

set -euo pipefail

# The minimum version of clang-format with the new options
CLANG_FORMAT_VERSION=10

CHECK_MODIFIED_ONLY=${CHECK_MODIFIED_ONLY:-false}

die() {
  while [ "$#" -gt 0 ]; do
    echo >&2 "$1"; shift
  done
  exit 1
}

# avoid unbound var
version=

check_clang_format_version() {
  cmd="$1"
  # version can be in 3rd or 4th position after the word "version"
  version=$("$cmd" --version \
    | grep -E -i -o " version [0-9]+.[0-9]+" \
    | grep -E -i -o "[0-9]+.[0-9]+")

  major=${version%%.*}
  [ $major -ge $CLANG_FORMAT_VERSION ]
}

clang_format=

if [ -n "${CLANG_FORMAT_PATH:-}" ]; then
  if [ ! -e "$(type -P "${CLANG_FORMAT_PATH}")" ]; then
    echo "CLANG_FORMAT_PATH='$CLANG_FORMAT_PATH' does not exist or is not executable" >&2
  else
    if check_clang_format_version "$CLANG_FORMAT_PATH"; then
      clang_format="$CLANG_FORMAT_PATH"
    else
      echo "CLANG_FORMAT_PATH='$CLANG_FORMAT_PATH'" >&2
    fi
  fi
else
  if [ ! -e "$(type -P clang-format)" ]; then
    echo "clang-format is not installed or not in the PATH." >&2
  else
    check_clang_format_version "clang-format" && \
      clang_format=clang-format
  fi
fi

if [ -z "$clang_format" ]; then
  die "clang-format version '$CLANG_FORMAT_VERSION' expected, but version '$version' found." \
      "Install LLVM version '$CLANG_FORMAT_VERSION' and rerun." \
      "Hint: export CLANG_FORMAT_PATH='/path/to/clang-format'"
fi

test_mode=

while [ "$#" -gt 0 ]; do
  arg="$1"
  case "$arg" in
    --test) test_mode=true; shift ;;
    --*)    die "Unknown argument: $arg" "Usage: $0 [--test]" ;;
    *)      break ;;
  esac
done

# Use this block for version 10 and above
if [ "$test_mode" = true ]; then
  opts="--dry-run"
  err="--Werror"
else
  opts="-i"
  err=
fi

if [ "$#" -gt 0 ]; then
  "$clang_format" --style=file "$opts" "$@"
elif [[ "$CHECK_MODIFIED_ONLY" == "1" ]]; then
  # exit early with 0 if no modified files found
  for path in $(git diff --name-only origin/main | (grep -E '.*\.(c|h|cpp|hpp)$' || exit 0)); do 
    [ -f $path ] || continue # skip if file does not exist (was removed)
    "$clang_format" --style=file "$opts" $err $path || die "C/C++ code formatting changes detected, Run '$0' to reformat."
  done   
else
  find . -name "*.[ch]" -or -name "*.[ch]pp" | \
    grep -E -v '.*/target/scala.*' | \
    grep -E -v ".*/(.venv|.scala-build)/" | \
    (xargs "$clang_format" --style=file "$opts" $err || die "C/C++ code formatting changes detected, Run '$0' to reformat.")
fi
